---
title: "動態記憶體分配"
description: "從 JavaScript 視角學習 C 語言動態記憶體分配，理解 malloc、free、記憶體洩漏，並對比 JavaScript 垃圾回收機制。"
---

# 動態記憶體分配

## 1. 概念介紹

### 從 JavaScript 垃圾回收到 C 手動記憶體管理

在 JavaScript 中，記憶體管理是自動的 - 垃圾回收器會自動釋放未使用的記憶體。而在 C 語言中，你必須手動使用 `malloc()`、`calloc()`、`realloc()` 和 `free()` 等函數來分配和釋放記憶體。

> 💡 **核心概念**：C 的手動記憶體管理給你精確的控制權，但需要仔細注意以防止記憶體洩漏和未定義行為。

## 2. 基礎記憶體分配

<UniversalEditor title="基礎記憶體分配" compare={true}>
```javascript !! js
// JavaScript - 自動記憶體管理
let arr = [1, 2, 3, 4, 5];
arr.push(6); // 陣列自動增長
arr = null; // 記憶體將被垃圾回收

// 動態物件建立
let obj = { name: "Alice", age: 30 };
obj = null; // 物件將被垃圾回收

// 無需手動記憶體管理
```

```c !! c
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 為整數分配記憶體
    int* ptr = (int*)malloc(sizeof(int));
    if (ptr == NULL) {
        printf("記憶體分配失敗！\n");
        return 1;
    }
    
    *ptr = 42;
    printf("值: %d\n", *ptr);
    
    // 始終釋放分配的記憶體
    free(ptr);
    ptr = NULL; // 良好實踐，避免懸空指標
    
    // 為陣列分配記憶體
    int* arr = (int*)malloc(5 * sizeof(int));
    if (arr == NULL) {
        printf("陣列分配失敗！\n");
        return 1;
    }
    
    // 初始化陣列
    for (int i = 0; i < 5; i++) {
        arr[i] = i + 1;
    }
    
    // 使用陣列
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    // 釋放陣列記憶體
    free(arr);
    arr = NULL;
    
    return 0;
}
```
</UniversalEditor>

## 3. 進階記憶體函數

<UniversalEditor title="進階記憶體函數" compare={true}>
```javascript !! js
// JavaScript - 動態陣列和物件
let numbers = [];
for (let i = 0; i < 10; i++) {
    numbers.push(i);
}

// 陣列自動調整大小
numbers.length = 20; // 擴展陣列
numbers.length = 5;  // 截斷陣列

// 物件屬性可以動態新增/刪除
let person = {};
person.name = "Bob";
person.age = 25;
delete person.age;
```

```c !! c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    // calloc - 分配並初始化為零
    int* zeros = (int*)calloc(5, sizeof(int));
    if (zeros == NULL) {
        printf("calloc 失敗！\n");
        return 1;
    }
    
    printf("calloc 初始化的值: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", zeros[i]); // 全為零
    }
    printf("\n");
    
    // realloc - 調整現有分配的大小
    int* resized = (int*)realloc(zeros, 10 * sizeof(int));
    if (resized == NULL) {
        printf("realloc 失敗！\n");
        free(zeros);
        return 1;
    }
    
    // 初始化新元素
    for (int i = 5; i < 10; i++) {
        resized[i] = i;
    }
    
    printf("realloc 後: ");
    for (int i = 0; i < 10; i++) {
        printf("%d ", resized[i]);
    }
    printf("\n");
    
    free(resized);
    
    return 0;
}
```
</UniversalEditor>

## 4. 記憶體洩漏和預防

<UniversalEditor title="記憶體洩漏預防" compare={true}>
```javascript !! js
// JavaScript - 自動清理
function createObjects() {
    let obj = { data: "重要資料" };
    // obj 在函數結束時將被垃圾回收
    // 無需手動清理
}

// 即使有循環參考，垃圾回收器也能處理
let obj1 = { name: "Alice" };
let obj2 = { name: "Bob" };
obj1.friend = obj2;
obj2.friend = obj1;
// 循環參考將被檢測並清理
```

```c !! c
#include <stdio.h>
#include <stdlib.h>

// 錯誤：記憶體洩漏 - 分配的記憶體未釋放
void badFunction() {
    int* ptr = (int*)malloc(sizeof(int));
    *ptr = 42;
    // 記憶體洩漏：ptr 未釋放！
    // ptr 超出作用域，記憶體丟失
}

// 正確：適當的記憶體管理
void goodFunction() {
    int* ptr = (int*)malloc(sizeof(int));
    if (ptr == NULL) {
        return; // 處理分配失敗
    }
    
    *ptr = 42;
    printf("值: %d\n", *ptr);
    
    free(ptr); // 始終釋放分配的記憶體
    ptr = NULL; // 防止懸空指標
}

// 記憶體洩漏檢測模式
void safeAllocation() {
    int* ptr = NULL;
    
    ptr = (int*)malloc(sizeof(int));
    if (ptr == NULL) {
        printf("分配失敗\n");
        return;
    }
    
    // 使用記憶體
    *ptr = 100;
    
    // 清理
    free(ptr);
    ptr = NULL;
}

int main() {
    goodFunction();
    safeAllocation();
    return 0;
}
```
</UniversalEditor>

## 5. 常見陷阱
- **記憶體洩漏**：忘記呼叫 `free()`
- **重複釋放**：對已釋放的記憶體再次呼叫 `free()`
- **懸空指標**：釋放記憶體後仍使用指標
- **緩衝區溢位**：寫入超出分配記憶體的範圍
- **空指標解參考**：未檢查 `malloc()` 是否返回 NULL

## 6. 練習題
1. 撰寫一個函數，為字串分配記憶體，複製給定字串到其中，並返回指標。記得稍後釋放它。
2. 建立一個函數，動態分配二維陣列並用值初始化它。
3. 撰寫一個程式，透過追蹤分配和釋放來示範記憶體洩漏檢測。

## 7. 效能分析
- 手動記憶體管理可能比垃圾回收更高效
- 頻繁的分配/釋放可能導致記憶體碎片
- 記憶體池可以提高小規模頻繁分配的性能
- 適當的記憶體管理對長時間執行的應用程式至關重要

---

> **小結**：C 的手動記憶體管理提供控制和性能，但需要紀律性。始終將 `malloc()` 與 `free()` 配對，檢查分配失敗，並使用 Valgrind 等工具檢測記憶體洩漏。 